import { Inject, Provide } from '@midwayjs/decorator';
import { BaseService, CoolCommException } from '@cool-midway/core';
import {
  App,
  Config,
  IMidwayApplication
} from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, LessThan } from 'typeorm';
import { BroadcastPlayRecordEntity } from '../entity/record';
import { BroadcastRecipeEntity } from '../entity/recipe';
import { Context } from '@midwayjs/koa';
import { BaseSysConfService } from '../../base/service/sys/conf';
import { PluginService } from '../../plugin/service/info';
import { v1 as uuid } from 'uuid';
import * as moment from 'moment'
import { BroadcastAudioEntity } from '../entity/audio';
import { UserAudioSettingEntity } from '../../user/entity/setting';
const fs = require("fs")
const exec = require('child_process').exec;
const fluent_ffmpeg = require('fluent-ffmpeg')
/**
 * 播放记录
 */
@Provide()
export class BroadcastRecipeService extends BaseService {
  @InjectEntityModel(BroadcastPlayRecordEntity)
  BroadcastPlayRecordEntity: Repository<BroadcastPlayRecordEntity>;

  @InjectEntityModel(BroadcastAudioEntity)
  BroadcastAudioEntity: Repository<BroadcastAudioEntity>;

  @InjectEntityModel(BroadcastRecipeEntity)
  BroadcastRecipeEntity: Repository<BroadcastRecipeEntity>;

  @InjectEntityModel(UserAudioSettingEntity)
  UserAudioSettingEntity: Repository<UserAudioSettingEntity>;

  @Inject()
  ctx: Context;

  @Config('module.broadcast')
  broConfig;

  @App()
  app: IMidwayApplication;

  @Inject()
  pluginService: PluginService;

  @Inject()
  BaseSysConfService: BaseSysConfService

  /**
   * 带背景音频的播放
   * @param elephantNumber 象数
   * @param audioId 背景音频Id
   */
  async miniBgAudio(value) {
    // 校验音频文件是否存在
    let error_message = await this.verifyAudioFile()
    if (error_message) {
      throw new CoolCommException(error_message);
    }
    // 获取音频名称
    let pathTitle = this.getPathTitle(value)
    value = value + "·"
    // 如果没登录，就执行不带背景音频的方法
    // if (!this.ctx.user) {
    //   return this.miniAudio(value)
    // }
    let audioId, volume, playMode, settingSwitch
    // 获取用户设置的音频信息
    if (this.ctx.user) {
      let settingInfo = await UserAudioSettingEntity.findOneBy({ userId: this.ctx?.user.id })
      // 如果没有音频设置信息，就手动添加一条默认记录
      if (!settingInfo) {
        let audioInfo = await BroadcastAudioEntity.findOneBy({ isDefault: 1 })
        let setting_audioId
        if (audioInfo) {
          setting_audioId = audioInfo.id
        } else {
          let rand = await this.nativeQuery(
            'SELECT id FROM broadcast_audio ORDER BY RAND() LIMIT 1; '
          );
          // setting_audioId = rand[0].id
          if (rand.length > 0) {
            setting_audioId = rand[0].id
          }
        }
        if (setting_audioId) {
          await UserAudioSettingEntity.save({ userId: this.ctx?.user.id, audioId: setting_audioId })
        } else {
          return this.miniAudio(value)
        }
      }
      let audio_setting_info = await UserAudioSettingEntity.findOneBy({ userId: this.ctx?.user.id })
      audioId = audio_setting_info.audioId
      volume = audio_setting_info.volume
      playMode = audio_setting_info.playMode
      settingSwitch = audio_setting_info.settingSwitch
      if (settingSwitch == 0) {
        // 如果关闭了背景音乐,就直接播放普通音频
        return this.miniAudio(value)
      } else {
        if (playMode == 1) {
          let rand = await this.nativeQuery(
            'SELECT id FROM broadcast_audio ORDER BY RAND() LIMIT 1; '
          );
          if (rand.length == 0) {
            return this.miniAudio(value)
          }
          // audioId = rand[0].id
          if (rand.length > 0) {
            audioId = rand[0].id
          }
        } else {
          // 如果没有audioId并且播放模式不是随机播放，就执行不带背景音频的方法
          if (!audioId) {
            return this.miniAudio(value)
          }
        }
      }
    } else {
      let audioInfo = await BroadcastAudioEntity.findOneBy({ isDefault: 1 })
      audioId = audioInfo.id
      volume = 2
      if (audioInfo) {
        audioId = audioInfo.id
      } else {
        let rand = await this.nativeQuery(
          'SELECT id FROM broadcast_audio ORDER BY RAND() LIMIT 1; '
        );
        if (rand.length > 0) {
          audioId = rand[0].id
        }

      }
      // 如果没有音频id,就播放没有背景音乐的音频
      if (!audioId) {
        return this.miniAudio(value)
      }
    }

    // 获取背景音频地址
    let { bg_detail, bgPath } = await this.getAudioDetail(audioId)
    // 调整音频时长和背景音频时长

    // 获取背景音频拼接命令行
    let { command: nan_command, output: nan_output } = this.getFFmpegCommand(value, pathTitle, 'nan')
    let { command: nv_command, output: nv_output } = this.getFFmpegCommand(value, pathTitle, 'nv')
    // 拼接象数音频 男、女
    await this.executeFFmpeg(`${nan_command}`)
    await this.executeFFmpeg(`${nv_command}`)
    // 混合背景音频
    let nan_whole_output = await this.splicingBgAudio({ path: nan_output, bgPath, gender: 'nan', pathTitle, volume })
    let nv_whole_output = await this.splicingBgAudio({ path: nv_output, bgPath, gender: 'nv', pathTitle, volume })
    // 上传混合好背景音频的总音频
    const file = await this.pluginService.getInstance('upload');
    await file.uploadWithKey(nan_whole_output, `mp3/nan/${pathTitle}.mp3`)
    await file.uploadWithKey(nv_whole_output, `mp3/nv/${pathTitle}.mp3`)
    // 处理完成后删除拼接好的象数音频
    fs.unlink(nan_output, (err) => { })
    fs.unlink(nv_output, (err) => { })
    fs.unlink(nan_whole_output, (err) => { })
    fs.unlink(nv_whole_output, (err) => { })
    // 当前背景音频记录+1 再查一遍是防止中途修改过默认音频
    let temp_bg_detail=await BroadcastAudioEntity.findOneBy({ id: audioId })
    temp_bg_detail.playCount = temp_bg_detail.playCount + 1
    await BroadcastAudioEntity.save(temp_bg_detail)
    // 保存记录
    return await this.editSaveInfo(value, pathTitle,audioId)
  }
  /**
   * 校验音频文件是否存在
   */
  async verifyAudioFile() {
    let list = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "kong"]
    let error_message = ""
    let error_nan_file = ""
    let error_nv_file = ""
    list.forEach(item => {
      let verify_nan_path = `${this.broConfig.savePath}/modules/broadcast/files/nan/${item}.mp3`
      let verify_nv_path = `${this.broConfig.savePath}/modules/broadcast/files/nv/${item}.mp3`
      if (!fs.existsSync(verify_nan_path)) {
        error_nan_file += item + "."
      }
      if (!fs.existsSync(verify_nv_path)) {
        error_nv_file += item + "."
      }
    })
    if (error_nan_file) {
      error_message = `缺少音频文件 男:${error_nan_file.slice(0, -1)} `
    }
    if (error_nv_file) {
      if (error_message) {
        error_message += ` 女:${error_nv_file.slice(0, -1)}`
      } else {
        error_message = `缺少音频文件 女:${error_nv_file.slice(0, -1)}`
      }
    }
    return error_message
    // let 
  }
  /**
   * 小程序播放
   * @param elephantNumber 象数
   */
  async miniAudio(value) {
    // 获取音频名称
    let pathTitle = this.getPathTitle(value)
    // 获取背景音频拼接命令行
    let { command: nan_command, output: nan_output } = this.getFFmpegCommand(value, pathTitle, 'nan')
    let { command: nv_command, output: nv_output } = this.getFFmpegCommand(value, pathTitle, 'nv')
    // 拼接象数音频 男、女
    await this.executeFFmpeg(`${nan_command}`)
    await this.executeFFmpeg(`${nv_command}`)
    const file = await this.pluginService.getInstance('upload');
    await file.uploadWithKey(nan_output, `mp3/nan/${pathTitle}.mp3`)
    await file.uploadWithKey(nv_output, `mp3/nv/${pathTitle}.mp3`)
    fs.unlink(nan_output, (err) => { })
    fs.unlink(nv_output, (err) => { })
    return await this.editSaveInfo(value, pathTitle)
  }

  /**
   * 根据id获取背景音频的地址
   * @param audioId 背景音频id
   */
  async getAudioDetail(audioId) {
    let savePath = this.broConfig.savePath
    // 获取背景音频详情
    let bg_detail = await BroadcastAudioEntity.findOneBy({ id: audioId })
    let bgPath = `${savePath}/modules/broadcast/files/background/${bg_detail.id}.mp3`
    // 判断文件夹里有没有这个音频
    if (!fs.existsSync(bgPath)) {
      // 如果没有,下载到文件夹,返回地址
      const download = require('download');
      // 数据
      const data = await download(bg_detail.url)
      fs.writeFileSync(
        `${savePath}/modules/broadcast/files/background/${bg_detail.id}.mp3`,
        data
      )
      console.log(`下载了音频${bg_detail.id}.mp3`)
    }
    return { bg_detail, bgPath }
  }

  /**
   * 混合背景音频
   * bgPath 背景音频路径
   * path 象数音频路径
   * gender 性别 nan nv
   */
  async splicingBgAudio({ path, bgPath, gender, pathTitle, volume }) {
    let savePath = this.broConfig.savePath
    let output = `${savePath}/modules/broadcast/files/background/${gender}/${pathTitle}.mp3`
    let duration_main = await this.getDuration(path)
    let duration_bg = await this.getDuration(bgPath)
    let command = ""
    // 如果主音频比背景音频长,就让背景音频一直循环
    if (duration_main > duration_bg) {
      command = `ffmpeg -i ${path} -filter_complex  "amovie=${bgPath}:loop=0,volume=${volume}[b];[b]atrim=0:${duration_main}[a];[0][a]amix=inputs=2:duration=first" ${output}`
    } else {
      // 如果背景音频比主音频长,就让主音频一直完整循环到不能完整循环为止
      let loop = Math.floor(duration_bg / duration_main)
      let total_duration = duration_main * loop
      command = `ffmpeg -stream_loop ${loop - 1} -i ${path} -i ${bgPath} -filter_complex "[1]atrim=0:${total_duration},volume=${volume}[b];[0][b]amix=inputs=2:duration=longest" ${output}`

    }
    return new Promise<string>((resolve, reject) => {
      exec(command, async (res) => {
        // console.log(res)
        if (res != null) {
          reject('音频合成失败')
        } else {
          resolve(output)
        }
      })
    })
    return new Promise<string>((resolve, reject) => {
      exec(`ffmpeg -i ${path} -i ${bgPath} -filter_complex "[1]volume=${volume}[bgm];[0][bgm]amix=inputs=2:duration=first" ${output}`, async (res) => {
        if (res != null) {
          reject('音频生成失败')
        } else {
          resolve(output)
        }
      })
    })
  }
  /**
   * 获取音频时长
   * @param filePath 
   * @returns 
   */
  async getDuration(filePath) {
    return new Promise<number>((resolve, reject) => {
      fluent_ffmpeg.ffprobe(filePath, (err, metadata) => {
        if (err) reject(err);
        else resolve(Math.round(metadata.format.duration * 100) / 100);
      });
    });
  }

  /**
    * 执行ffmpeg的命令,目前只在拼接音频时用
    * @param command 命令语句
  */
  executeFFmpeg(command) {
    return new Promise<void>((resolve, reject) => {
      exec(command, async (res) => {
        if (res != null) {
          reject('音频生成失败')
        } else {
          resolve(res)
        }
      });
    })
  }
  /**
    * 微信小程序播放
    * @param elephantNumber
  */
  async wxAdd(value) {
    let pathTitle = this.getPathTitle(value)
    let { nanCopy, nvCopy, nanStr, nvStr } = this.getElephantPath(value, pathTitle)
    await this.setFFmpeg(nanStr, nanCopy, pathTitle + '.mp3', 'nan')
    await this.setFFmpeg(nvStr, nvCopy, pathTitle + '.mp3', 'nv')
    fs.unlink(nanCopy, (err) => { })
    fs.unlink(nvCopy, (err) => { })
    return await this.editSaveInfo(value, pathTitle)
  }
  /**
   * 获取象数名称
   * @param value
   * @returns pathTitle
   */
  getPathTitle(value) {
    let elephantNumber = value.replace(/[^(\d|\·\.)]/g, '')
    let fileTile = elephantNumber.replace(/[^(\d)]/g, '')
    return fileTile + uuid()
  }

  /**
   * 获取象数音频的拼接路径 有杂音
   * @param value 象数
   * @param pathTitle 象数标题
   */
  getElephantPath(value, pathTitle) {
    let savePath = this.broConfig.savePath
    let nanStr = "concat:"
    let nvStr = "concat:"
    let nanCopy = `${savePath}/modules/broadcast/files/nan_temp/${pathTitle}.mp3`
    let nvCopy = `${savePath}/modules/broadcast/files/nv_temp/${pathTitle}.mp3`
    for (let i = 0; i < value.length; i++) {
      if (value[i] == "·" || value[i] == ".") {
        nanStr += `${savePath}/modules/broadcast/files/nan/kong.mp3|`
        nvStr += `${savePath}/modules/broadcast/files/nv/kong.mp3|`
      } else {
        nanStr += `${savePath}/modules/broadcast/files/nan/${value[i]}.mp3|`
        nvStr += `${savePath}/modules/broadcast/files/nv/${value[i]}.mp3|`
      }
    }
    nanStr += `${savePath}/modules/broadcast/files/nan/kong.mp3`
    nvStr += `${savePath}/modules/broadcast/files/nv/kong.mp3`
    return { nanCopy, nvCopy, nanStr, nvStr }
  }

  /**
   * 处置保存播放记录的表单
   */
  async editSaveInfo(value, pathTitle,audioId=null) {
    let saveInfo = {
      playPath: "",
      info: {}
    }
    if (this.ctx.user) {
      let params = {
        elephantNumber: value.slice(0, -1),
        userId: this.ctx.user.id,
        audioId
      }
      saveInfo.info = await this.add(params)
    }
    saveInfo.playPath = pathTitle + '.mp3'
    return saveInfo
  }

  /**
   * 拼接音频并上传到腾讯云
   * @param elephantNumber
   */
  setFFmpeg(path, copyPath, pathTitle, sexual) {
    return new Promise<void>((resolve, reject) => {
      exec(`ffmpeg -i "${path}" -acodec copy "${copyPath}" `, async (res) => {
        if (res != null) {
          reject('音频生成失败')
        }
        const file = await this.pluginService.getInstance('upload');
        resolve(await file.uploadWithKey(copyPath, `mp3/${sexual}/${pathTitle}`))
      });
    })
  }
  /**
   * 保存象数播放记录
   * @param elephantNumber
   */
  async add(param) {
    let afterInfo = await BroadcastPlayRecordEntity.findOneBy(param)

    if (afterInfo) {
      param.id = afterInfo.id
      param.count = afterInfo.count + 1
    }
    param.recipeId = -1
    param.recipeInfo = {}
    let info = await BroadcastPlayRecordEntity.save(param)
    return info
  }
  /**
  * 清除播放记录
  */
  async clear() {
    const keepDay = await this.BaseSysConfService.getValue('recordKeep');
    if (keepDay) {
      const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate();
      await BroadcastPlayRecordEntity.delete({ updateTime: LessThan(beforeDate) });
    }
  }
  /**
   * 获取ffmpeg拼接的命令行
   * gender 性别 nan nv
   * @param value 象数
   * @param pathTitle 象数标题
   */
  getFFmpegCommand(value, pathTitle, gender) {
    let savePath = this.broConfig.savePath
    let output = `${savePath}/modules/broadcast/files/${gender}_temp/${pathTitle}.mp3`
    let str_i = ""
    let str_complex = ""
    let str_acrossfade = ""
    let duration = 0.05
    let crossing_duration = 0.05
    // value = value + "·"
    for (let i = 0; i < value.length; i++) {
      if (value[i] == "·" || value[i] == ".") {
        str_i += ` -i ${savePath}/modules/broadcast/files/${gender}/kong.mp3`
      } else {
        str_i += ` -i ${savePath}/modules/broadcast/files/${gender}/${value[i]}.mp3`
      }
      str_complex += `[${i}]afade=t=in:st=0:d=${duration},afade=t=out:st=0.95:d=${duration}[a${i}];`
    }
    let str_complex_list = str_complex.split(";").filter(Boolean)
    let temp_name = ""
    str_complex_list.forEach((item, index) => {
      if (index < str_complex_list.length - 1) {
        if (index != 0) {
          if (temp_name == "") {
            str_acrossfade += `[a${index - 1}][a${index}]acrossfade=d=${crossing_duration}[tmp${index}];`
          } else {
            str_acrossfade += `[${temp_name}][a${index}]acrossfade=d=${crossing_duration}[tmp${index}];`
          }
          temp_name = `tmp${index}`
        }
      } else if (index == str_complex_list.length - 1) {
        str_acrossfade += `[${temp_name}][a${index}]acrossfade=d=${crossing_duration}[out];`
        temp_name = ""
      }
    })
    let command = `ffmpeg ${str_i} -filter_complex "${str_complex} ${str_acrossfade}" -map "[out]" ${output}`
    return { command, output }
  }
}
